#include "Network.h"

#include <boost/thread.hpp>

#define NETWORK_PORT	8688
#define NETWORK_PORT_NAME	"8688"

using namespace boost::asio::ip;

bool Network::_isReceiving = false;
boost::asio::io_service* Network::io_service = NULL;
udp::socket* Network::sendSocket = NULL;
udp::socket* Network::receiveSocket = NULL;
udp::endpoint Network::localEndpoint;

boost::mutex Network::_networkMutex;
boost::mutex Network::_receiveBufferMutex;
boost::thread Network::_sendThread;
boost::thread Network::_receiveThread;
std::map<boost::asio::ip::udp::endpoint, ThreadSafeQueue<MessageHeader*> *> Network::_receiverQueue;
std::map<ID, boost::asio::ip::udp::endpoint> Network::_receiveBuffer;

// initialize network
void Network::init(bool listening)
{

	io_service = new boost::asio::io_service();


	udp::resolver resolver(*io_service);
	udp::resolver::query query(host_name(), "");
	udp::resolver::iterator iterator = resolver.resolve(query);
// FIXME: evil hack! assuming that the second entry is valid
	iterator++;
	localEndpoint = *iterator;

	sendSocket = new udp::socket (*io_service);
	sendSocket->open(udp::v4());

	boost::asio::socket_base::non_blocking_io command(true);

	if (listening)
	{
		receiveSocket = new udp::socket(*io_service, udp::endpoint(udp::v4(), NETWORK_PORT));
		_sendThread = boost::thread(boost::bind(&Network::sendThreadMain));
		// localEndpoint = getEndpoint(serverAddress);
		localEndpoint.port(NETWORK_PORT);
	}
	else
	{
		receiveSocket = sendSocket;

		srand(time(NULL));
		localEndpoint.port(rand());
	}
}

void Network::startReceiving()
{
	_isReceiving = true;
	_receiveThread = boost::thread(boost::bind(&Network::receiveThreadMain));
}

void Network::stopReceiving()
{
	_isReceiving = false;
	_receiveThread.join();
}

// send message 
void Network::sendMessage(MessageHeader *msgHeader)
{
	boost::mutex::scoped_lock (_networkMutex);
	std::map<boost::asio::ip::udp::endpoint, ThreadSafeQueue<MessageHeader*> *>::iterator mapIt;
	for(mapIt = _receiverQueue.begin(); mapIt != _receiverQueue.end(); mapIt++)
	{
		mapIt->second->append(msgHeader);
	}
}

void Network::sendMessageNow(MessageHeader *message)
{
		boost::mutex::scoped_lock (_networkMutex);

			std::map<boost::asio::ip::udp::endpoint, ThreadSafeQueue<MessageHeader*> *>::iterator mapIt = _receiverQueue.begin();
			while(mapIt != _receiverQueue.end())
			{
				try{
					sendSocket->send_to(boost::asio::buffer((void *)message, message->_length), mapIt->first, 0);
					mapIt++;
				}
				catch(...){
					throw NetworkException(SEND_ERROR);
					ThreadSafeQueue<MessageHeader*> *messageQueue = mapIt->second;
					if (messageQueue != NULL) delete messageQueue;
					// erase current element and return next
					mapIt = _receiverQueue.erase(mapIt);
				}
			}
}

void Network::sendThreadMain()
{
	while(true)
	{
		unsigned char buffer[MAX_SIZE];
		std::map<boost::asio::ip::udp::endpoint, ThreadSafeQueue<MessageHeader*> *>::iterator mapIt = _receiverQueue.begin();
		while(mapIt != _receiverQueue.end())
		{
			{
				boost::mutex::scoped_lock (_networkMutex);
				try
				{

					unsigned int bytesPacked = 0;
					// copy messages to streambuffer as long as queue is not empty and buffer has enough space for biggest possible message
					while(mapIt->second && !mapIt->second->empty())
					{
						if (sizeof(buffer) + 1032 < MAX_SIZE) break;
						MessageHeader *message = mapIt->second->get();
						memcpy(&buffer[bytesPacked], message, message->_length);
						bytesPacked += message->_length;
					}
					sendSocket->send_to(boost::asio::buffer((void *)buffer, bytesPacked), mapIt->first, 0);
					mapIt++;
				}
				catch(...)
				{
					throw NetworkException(SEND_ERROR);
					ThreadSafeQueue<MessageHeader*> *messageQueue = mapIt->second;
					if (messageQueue != NULL) delete messageQueue;
					// erase current element and return next
					mapIt = _receiverQueue.erase(mapIt);
				}

			}
			boost::posix_time::milliseconds waitTime(SEND_PERIOD / _receiverQueue.size());
			boost::this_thread::sleep( waitTime );
		}
	}
}

void Network::receiveThreadMain()
{
	while ( _isReceiving ) 
	{
		receivePacket();
	}
}

void Network::receiveMessage()
{
	boost::mutex::scoped_lock (_receiveBufferMutex);
	std::map<ID, boost::asio::ip::udp::endpoint>::iterator _receiveBufferIt;
	for ( _receiveBufferIt = _receiveBuffer.begin(); _receiveBufferIt != _receiveBuffer.end(); _receiveBufferIt++ ) 
	{
		Object::handleMessage(_receiveBufferIt->first, _receiveBufferIt->second);
	}
	_receiveBuffer.clear();
}

// client recieve message
void Network::receivePacket()
{
	boost::mutex::scoped_lock lock(_networkMutex);
	MessageHeader *receivedMsg;
	unsigned char buffer[MAX_SIZE];
	size_t bytesUnpacked = 0;
	size_t bytesReceived;
	udp::endpoint senderEndpoint;

	try
	{

		boost::mutex::scoped_lock (_receiveBufferMutex);
		bytesReceived = receiveSocket->receive_from(boost::asio::buffer(buffer, sizeof(buffer)), senderEndpoint, 0);

		while (bytesUnpacked < bytesReceived) // read message as long as end is not reached
		{
			receivedMsg = (MessageHeader *)&buffer[bytesUnpacked];
			// read one message and give it to object
			Object::onReceiveMessage(receivedMsg);

			
			_receiveBuffer[receivedMsg->_id] = senderEndpoint;
			bytesUnpacked += receivedMsg->_length;
		}
	}
	catch(...)
	{
		throw NetworkException(RECEIVE_ERROR);
	}
}

void Network::addReceiverEndpoint(boost::asio::ip::udp::endpoint endpoint)
{
	ThreadSafeQueue<MessageHeader*> *messageQueue = new ThreadSafeQueue<MessageHeader*>();
	Network::_receiverQueue[endpoint] = messageQueue;
}

void Network::removeReceiverEndpoint(boost::asio::ip::udp::endpoint endpoint)
{
	ThreadSafeQueue<MessageHeader*> *messageQueue;
	messageQueue = Network::_receiverQueue[endpoint];
	if (messageQueue != NULL)
	{
		delete messageQueue;
	}
	Network::_receiverQueue.erase(endpoint);
}


boost::asio::ip::udp::endpoint Network::getEndpoint( const char* hostName )
{
    udp::resolver resolver(*io_service);
	udp::resolver::query query(udp::v4(), hostName, NETWORK_PORT_NAME);
    udp::endpoint receiver_endpoint = *resolver.resolve(query);

	return receiver_endpoint;
}

boost::asio::ip::udp::endpoint Network::getLocalEndpoint() 
{
	return localEndpoint;
}

